﻿using Fast.RemoteRequest;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Extensions.Http;
using Polly.Timeout;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Fast
{
    [SkipScan]
    public static class HttpRemoteRequestServiceCollectionExtensions
    {
        public static IServiceCollection AddRemoteRequest(this IServiceCollection services)
        {
            Check.NotNull(services, nameof(services));

            var pollyOptions = App.GetConfig<List<PollyHttpClientConfigureOptions>>("Polly");
            if (pollyOptions is { Count: > 0 })
            {
                pollyOptions.ForEach(services.AddPollyHttpClient);
            }
            else
            {
                services.AddHttpClient();
            }

            //添加远程请求代理
            services.AddDispatchProxyForInterface<HttpDynamicProxy, IHttpDynamicProxy>();

            services.AddTransient<IHttpRemoteRequest, HttpRemoteRequest>();
            services.AddOptions<HttpRemoteRequestOptions>();
            return services;
        }

        private static void AddPollyHttpClient(this IServiceCollection services,
            PollyHttpClientConfigureOptions options)
        {
            var serviceNames = options.ServiceNames;
            if (serviceNames == null || serviceNames.Count == 0)
            {
                throw new ArgumentNullException(nameof(serviceNames), "请配置Polly.ServiceNames");
            }

            foreach (var serviceName in serviceNames)
            {
                //策略顺序（从下(内)到上(外)）：熔断->超时->重试->降级
                services.AddHttpClient(serviceName)
                    .BuildFallbackPolicyHandler(options.FallbackHttpResponseMessage, options.FallbackHttpResponseStatus)
                    .BuildRetryPolicyHandler(options.RetryCount)
                    .BuildTimeoutPolicyHandler(options.TimeoutTime)
                    .BuildCircuitBreakerPolicyHandler(options.CircuitBreakerOpenFallCount, options.CircuitBreakerDownTime);
            }
        }

        /// <summary>
        /// 重试策略
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="retryCount"></param>
        /// <returns></returns>
        private static IHttpClientBuilder BuildRetryPolicyHandler(this IHttpClientBuilder builder, int retryCount)
        {
            if (retryCount <= 0) return builder;

            var retryPolicy = HttpPolicyExtensions
                .HandleTransientHttpError()
                .Or<TimeoutRejectedException>()
                .WaitAndRetryAsync(retryCount, retryAttempt => TimeSpan.FromSeconds(2),
                    (res, timeSpan, rCount, context) =>
                    {
                        Log($"在请求 {context.OperationKey} 时经过 {timeSpan.TotalSeconds}s 后使用重试策略执行第 {rCount} 次重试, 异常原因: {res.Exception}.");
                    });

            builder.AddPolicyHandler(retryPolicy);
            return builder;
        }

        /// <summary>
        /// 超时策略
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="timeoutTime"></param>
        /// <returns></returns>
        private static IHttpClientBuilder BuildTimeoutPolicyHandler(this IHttpClientBuilder builder, int timeoutTime)
        {
            if (timeoutTime > 0)
                builder.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(timeoutTime)));
            return builder;
        }

        /// <summary>
        /// 降级策略
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="httpResponseMessage"></param>
        /// <param name="httpResponseStatus"></param>
        /// <returns></returns>
        private static IHttpClientBuilder BuildFallbackPolicyHandler(this IHttpClientBuilder builder,
            string httpResponseMessage, int httpResponseStatus)
        {
            if (httpResponseStatus < 1 || string.IsNullOrEmpty(httpResponseMessage))
                return builder;

            var fallbackResponse = new HttpResponseMessage
            {
                Content = new StringContent(httpResponseMessage),
                StatusCode = (HttpStatusCode)httpResponseStatus
            };

            var fallbackPolicy = Policy<HttpResponseMessage>.HandleInner<Exception>().FallbackAsync(
                fallbackResponse, async result =>
                {
                    Log($"开始降级,异常消息：{result.Exception.Message},降级内容响应：{await fallbackResponse.Content.ReadAsStringAsync()}");

                    await Task.CompletedTask;
                });
            builder.AddPolicyHandler(fallbackPolicy);

            return builder;
        }

        /// <summary>
        /// 熔断策略/断路器策略
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="circuitBreakerOpenFallCount"></param>
        /// <param name="circuitBreakerDownTime"></param>
        private static IHttpClientBuilder BuildCircuitBreakerPolicyHandler(this IHttpClientBuilder builder,
            int circuitBreakerOpenFallCount, int circuitBreakerDownTime)
        {
            if (circuitBreakerOpenFallCount < 1 || circuitBreakerDownTime < 1)
                return builder;

            var logger = App.GetRequiredService<ILogger<IHttpRemoteRequest>>();
            var breakerPolicy = Policy<HttpResponseMessage> // HttpResponseMessage 为HttpClient的返回值
                .Handle<Exception>() //捕获Exception异常
                .OrResult(res => // 过滤使用策略可处理的结果
                    res.StatusCode is HttpStatusCode.InternalServerError or HttpStatusCode.RequestTimeout)
                .CircuitBreakerAsync(
                    circuitBreakerOpenFallCount, // 出现指定次数异常
                    TimeSpan.FromSeconds(circuitBreakerDownTime), //熔断指定时长
                    (res, ts) =>
                    {
                        //熔断器打开状态，此时对目标服务的调用都直接返回错误，熔断周期内不会走网络请求，当熔断周期结束时进入半开状态；
                        Log($"断路器开启,时间：{ts.TotalSeconds}s,异常消息：{res.Result}");
                    },
                    () =>
                    {
                        //关闭状态下正常发生网络请求，但会记录符合熔断条件的连续执行次数，如果错误数量达到设定的阈值（如果在没有达到阈值之前恢复正常，之前的累积次数将会归零），熔断状态进入到打开状态；
                        //断路器重置事件触发
                        Log($"{DateTime.Now:yyyy-MM-dd}:断路器关闭");
                    },
                    () =>
                    {
                        //半开状态下允许定量的服务请求，如果调用都成功则认为恢复了，关闭熔断器，否则认为还没好，又回到熔断器打开状态；
                        //断路器半开启事件触发
                        Log($"{DateTime.Now:yyyy-MM-dd}断路器半开启(一会开，一会关)");
                    }
                );
            builder.AddPolicyHandler(breakerPolicy);

            return builder;
        }

        /// <summary>
        /// 记录Log
        /// </summary>
        /// <param name="message"></param>
        private static void Log(string message)
        {
            var logger = App.GetRequiredService<ILogger<IHttpRemoteRequest>>();
            // 降级打印异常
            logger.LogWarning(message);
            // 打印失败请求
            App.PrintToMiniProfiler(MiniProfilerCategory.HttpClient.GetDescription(), "Failed", message, true);
        }
    }
}